package org.liujing.jsw.host;

import java.util.*;
import java.util.logging.*;
import java.io.*;
import com.google.gwt.core.ext.*;
import com.google.gwt.core.ext.typeinfo.*;
import org.liujing.jsw.host.*;
import org.liujing.jsw.client.bundle.*;

public class BackgroundImgGenerator extends Generator{
	private static Logger log = Logger.getLogger(BackgroundImgGenerator.class.getName());
	
	private static class ImageMethodInfo
	{
		public String name;
		public String path;
		public int imgType;
		public JMethod method;
		private BackgroundImageBoudler.BImage image;
		
		public ImageMethodInfo(String name, String path, int imgType, JMethod method){
			this.name = name;
			this.path = path;
			this.imgType = imgType;
			this.method = method;
		}
		
		public void setImage(BackgroundImageBoudler.BImage image){
			this.image = image;
		}
		
		public BackgroundImageBoudler.BImage getImage(){
			return image;
		}
	}
	
	private class GeneratorData{
		public JClassType type;
		public GeneratorContext ctx;
		public String packageName;
		public String packagePath;
		public String bundlerPath;
		public String targetXPath;
		public String targetYPath;
		private List<ImageMethodInfo> imageMethodInfos;
		public File tempDirFile;
		public File cacheXFile;
		public File cacheYFile;
		public File cacheImplFile;
		public String substituteClassName;
		
		public GeneratorData(){
			
		}
		
		public List<ImageMethodInfo> getImageMethodInfos(TreeLogger treeLog){
			if(imageMethodInfos == null)
				imageMethodInfos = traverseMethods(treeLog, this);
			return imageMethodInfos;
		}
	}
	
	public String generate(TreeLogger treeLog, GeneratorContext ctx, String requestedClass)
	throws UnableToCompleteException
	{
		try{
		//treeLog.log(treeLog.INFO,  "generate background image for " + requestedClass);
		TypeOracle typeO = ctx.getTypeOracle();
		GeneratorData data = new GeneratorData();
		data.type = typeO.findType(requestedClass);
		JMethod[] methods = null;
		
		
		data.packageName = data.type.getPackage().getName();
		data.packagePath = data.packageName.replaceAll("\\.", "/");
		data.bundlerPath = data.packagePath + "/" + data.type.getName();
		data.targetXPath = "bundledimg/" + data.bundlerPath + "_x.png";
		data.targetYPath = "bundledimg/" + data.bundlerPath + "_y.png";
		data.substituteClassName = "_" + data.type.getName();
		data.ctx = ctx;
		
		OutputStream streamX = data.ctx.tryCreateResource(treeLog, data.targetXPath);
		OutputStream streamY = data.ctx.tryCreateResource(treeLog, data.targetYPath);
		if(streamX == null || streamY == null)
			return data.packageName + "." + data.substituteClassName;
		if(checkUptotime(treeLog, data)){
			treeLog.log(treeLog.INFO,  "Up to date, read from cache");
			genResourceByCache(treeLog, data, streamX, streamY);
			ctx.commitResource(treeLog, streamX);
			ctx.commitResource(treeLog, streamY);
			return genImplementationByCache(treeLog, data);
		}else{
			treeLog.log(treeLog.INFO,  "Not up to date, generate resource");
			generateResource(treeLog, data, streamX, streamY);
			ctx.commitResource(treeLog, streamX);
			ctx.commitResource(treeLog, streamY);
			return generateImplementation(treeLog, data);
		}
		//return generateImplementation(treeLog, data);
		}catch(IOException ioe){
			treeLog.log(treeLog.ERROR, "", ioe);
			throw new UnableToCompleteException();
		}
	}
	
	private boolean checkUptotime(TreeLogger treeLog, GeneratorData data)
	{
		boolean checked = true;
		String srcDir = System.getProperty("srcDir");
		String buildDir = System.getProperty("buildDir");
		String cacheDir = System.getProperty("tempDir");
		if(srcDir == null || buildDir == null || cacheDir == null){
			treeLog.log(treeLog.INFO, 
				"To speed up generating image resource, set system property for \"srcDir\", \"buildDir\" & \"tempDir\"");
			return false;
		}
		File f_srcDir = new File(srcDir);
		File f_cacheDir = new File(cacheDir);
		data.tempDirFile = f_cacheDir;
		data.cacheXFile = new File(data.tempDirFile, data.targetXPath);
		data.cacheYFile = new File(data.tempDirFile, data.targetYPath);
		
		File f_targetX = new File(buildDir, data.targetXPath);
		File f_targetY = new File(buildDir, data.targetYPath);
		data.cacheImplFile = new File(new File(data.tempDirFile, data.packagePath), data.substituteClassName + ".java");
		if( (!f_srcDir.exists()) || ( (!f_targetX.exists()) && (!f_targetY.exists()) )){
			treeLog.log(treeLog.INFO, f_srcDir.getAbsolutePath() + " or " + f_targetX.getAbsolutePath() + " not exists");
			checked = false;
		}
		//if(!f_cacheDir.exists()){
		//	f_cacheDir.mkdirs();
		//	treeLog.log(treeLog.INFO, "create folder:" + f_cacheDir.getAbsolutePath());
		//}
		
		
		
		if((!data.cacheXFile.exists()) && (!data.cacheYFile.exists())){
			data.cacheXFile.getParentFile().mkdirs();
			treeLog.log(treeLog.INFO, "create folder for " + data.cacheXFile.getParentFile().getAbsolutePath());
			checked = false;
		}
		
		
		if(!data.cacheImplFile.exists()){
			data.cacheImplFile.getParentFile().mkdirs();
			treeLog.log(treeLog.INFO, "create folder for " + data.cacheImplFile.getParentFile().getAbsolutePath());
			checked = false;
		}
		
		if(!checked)
			return checked;
		treeLog.log(treeLog.INFO, "src: "+  (f_srcDir.isFile() ? f_srcDir.getPath() : new File(f_srcDir, data.bundlerPath + ".java").getPath()));
		long srcModTime = f_srcDir.isFile() ? f_srcDir.lastModified() : new File(f_srcDir, data.bundlerPath + ".java").lastModified();
		treeLog.log(treeLog.INFO, "time:" + srcModTime);
		long targetModTime = f_targetX.lastModified();
		
		if(srcModTime > f_targetX.lastModified() ||srcModTime > f_targetY.lastModified() )
			return false;
		treeLog.log(treeLog.INFO, "target time:" + f_targetX.lastModified() + ", " + f_targetY.lastModified());
		List<ImageMethodInfo> imageInfos = data.getImageMethodInfos(treeLog);
		Iterator<ImageMethodInfo> infos = imageInfos.iterator();
		while(infos.hasNext()){
			ImageMethodInfo info = infos.next();
			File imageFile = new File(info.path);
			long modTime =  imageFile.lastModified();
			if(modTime > targetModTime){
				treeLog.log(treeLog.INFO,  "Image "+ info.path + " is newly updated.");
				return false;
			}
		}
		return true;
	}
	
	protected List<ImageMethodInfo> traverseMethods(TreeLogger treeLog, GeneratorData data)
	{
		JMethod[] methods = data.type.getMethods();
		List<ImageMethodInfo> imgMethodInfos = new ArrayList(methods.length);
		for(JMethod method: methods){
			
			if("org.liujing.jsw.client.bundle.BundledImage".equals(method.getReturnType().getQualifiedSourceName())){
				String name = method.getName();
				int imgType = ImageConstants.X_REPEAT_TYPE;
				BundledImageInfo info = method.getAnnotation(BundledImageInfo.class);
				if(info == null)
					continue;
				String suffix = ".jpg";
				String path = null;
				if(info != null){
					imgType = info.type();
					suffix = info.suffix();
					
					if(info.path().length()>0)
						path = "/" + data.packagePath +"/" + info.path();
					else{
						if(info.name().length()>0)
							name = info.name();
						path = "/" + data.bundlerPath +"/" + name + suffix;
					}
				}else{
					treeLog.log(treeLog.INFO,  "null img info for " + name);
				}
				
				treeLog.log(treeLog.INFO,  "bundle image: " + path);				
				imgMethodInfos.add(new ImageMethodInfo(name, path, imgType, method));
			}
		}
		return imgMethodInfos;
	}
	
	private void generateResource(TreeLogger treeLog, GeneratorData data, OutputStream streamX, OutputStream streamY)
	throws IOException, UnableToCompleteException
	{
		//OutputStream stream = data.ctx.tryCreateResource(treeLog, data.targetPath);
		if(streamX == null)
			return;
		if(data.tempDirFile != null){
			treeLog.log(treeLog.INFO, "write images info to " + data.cacheXFile.getPath());
			BufferedOutputStream tempoutX = new BufferedOutputStream(new FileOutputStream(data.cacheXFile));
			BufferedOutputStream tempoutY = new BufferedOutputStream(new FileOutputStream(data.cacheYFile));
			generateResourceTo(treeLog, data, tempoutX, tempoutY);
			tempoutX.close();
			tempoutY.close();
			
			BufferedInputStream in = new BufferedInputStream(new FileInputStream(data.cacheXFile));
			byte[] buf = new byte[1024];
			int count = in.read(buf, 0, 1024);
			while(count >= 0){
				streamX.write(buf, 0, count);
				count = in.read(buf, 0, 1024);
			}
			in.close();
			in = new BufferedInputStream(new FileInputStream(data.cacheYFile));
			//byte[] buf = new byte[1024];
			count = in.read(buf, 0, 1024);
			while(count >= 0){
				streamY.write(buf, 0, count);
				count = in.read(buf, 0, 1024);
			}
			in.close();
		}else{
			generateResourceTo(treeLog, data, streamX, streamY);
		}
		//data.ctx.commitResource(treeLog, stream);
	}
	
	private void generateResourceTo(TreeLogger treeLog, GeneratorData data, OutputStream streamX, OutputStream streamY)
	throws IOException, UnableToCompleteException
	{
		BackgroundImageBoudler b = new BackgroundImageBoudler(data.type.getName());
		Iterator<ImageMethodInfo> infos = data.getImageMethodInfos(treeLog).iterator();
		//HashMap<String, BackgroundImageBoudler.BImage> images = new HashMap();
		while(infos.hasNext()){
			ImageMethodInfo info = infos.next();
			InputStream imageIs = BackgroundImgGenerator.class.getResourceAsStream(info.path);
			BackgroundImageBoudler.BImage bimage = b.addImage(info.imgType, info.name, info.path);
			info.setImage(bimage);
			//images.put(info.name ,bimage);
		}
		b.combineXImages(streamX);
		b.combineYImages(streamY);
		//return images;
	}
	
	private void genResourceByCache(TreeLogger treeLog, GeneratorData data, OutputStream streamX, OutputStream streamY)
	throws IOException, UnableToCompleteException
	{
		//OutputStream stream = data.ctx.tryCreateResource(treeLog, data.targetPath);
		if(streamX == null)
			return;
		BufferedInputStream in = new BufferedInputStream(new FileInputStream(data.cacheXFile));
		byte[] buf = new byte[1024];
		int count = in.read(buf, 0, 1024);
		while(count >= 0){
			streamX.write(buf, 0, count);
			count = in.read(buf, 0, 1024);
		}
		in.close();
		in = new BufferedInputStream(new FileInputStream(data.cacheYFile));
		
		count = in.read(buf, 0, 1024);
		while(count >= 0){
			streamY.write(buf, 0, count);
			count = in.read(buf, 0, 1024);
		}
		in.close();
		//data.ctx.commitResource(treeLog, stream);
	}
	
	private String generateImplementation(TreeLogger treeLog, GeneratorData data)
	throws FileNotFoundException, IOException
	{
		//String substituteClassName = "_" + data.type.getName();
		PrintWriter writer = data.ctx.tryCreate(treeLog, data.packageName , data.substituteClassName);
				
		if(writer != null){
			treeLog.log(treeLog.INFO,  "generating class " + data.substituteClassName);
			if(data.tempDirFile != null){
				File tempFile = data.cacheImplFile;
				
				PrintWriter tempWriter = new PrintWriter(tempFile);
				generateImplementationTo(treeLog, data, tempWriter);
				tempWriter.close();
				
				BufferedReader in = new BufferedReader(new FileReader(tempFile));
				char[] buf = new char[1024];
				int count = in.read(buf, 0, 1024);
				while(count >= 0){
					writer.write(buf, 0, count);
					count = in.read(buf, 0, 1024);
				}
				in.close();
			}else{
				generateImplementationTo(treeLog, data, writer);
			}
			writer.close();
			data.ctx.commit(treeLog, writer);
		}
		treeLog.log(treeLog.INFO, "done");
		return data.packageName + "." + data.substituteClassName;
	}
	
	private void generateImplementationTo(TreeLogger treeLog, GeneratorData data, PrintWriter writer)
	{
		if(writer != null){			
			writer.println("package ");
			writer.print(data.packageName);
			writer.println(";\nimport org.liujing.jsw.client.bundle.BundledImage;");
			writer.println("");
			writer.print("public class _");
			writer.print(data.type.getName());
			writer.print(" implements ");
			writer.print(data.type.getQualifiedSourceName());
			writer.println("{");
			writer.println("\t");
			int i = 0;
			Iterator<ImageMethodInfo> methodInfos = data.getImageMethodInfos(treeLog).iterator();
			while(methodInfos.hasNext()){
				ImageMethodInfo info = methodInfos.next();
				 
				if("org.liujing.jsw.client.bundle.BundledImage".equals(info.method.getReturnType().getQualifiedSourceName())){
					treeLog.log(treeLog.INFO,  info.method.getName());
					writer.print("private BundledImage _");
					writer.print(info.method.getName());
					writer.print(" = new BundledImage(");
					BackgroundImageBoudler.BImage bimage = info.getImage();
					writer.print("\"");
					writer.print(bimage.isRepeatX()? data.targetXPath: data.targetYPath);
					writer.print("\",");
					writer.print(bimage.getX());
					writer.print(",");
					writer.print(bimage.getY());
					writer.print(",");
					writer.print(bimage.getWidth());
					writer.print(",");
					writer.print(bimage.getHeight());
					writer.print(",");
					writer.print(String.valueOf(bimage.isRepeatX()));
					writer.print(",");
					writer.print(String.valueOf(bimage.isRepeatY()));
					writer.println(");\n");
					
					writer.print("public BundledImage ");
					writer.print(info.method.getName());
					writer.println("(){");
					writer.print("\treturn _");
					writer.print(info.method.getName());
					writer.println(";\n}");					
					i++;
				}
				
			}
			writer.println("}");
		}
		treeLog.log(treeLog.INFO, "done");
	}
	
	private String genImplementationByCache(TreeLogger treeLog, GeneratorData data)
	throws FileNotFoundException, IOException
	{		
		String substituteClassName = data.substituteClassName;
		PrintWriter writer = data.ctx.tryCreate(treeLog, data.packageName , substituteClassName);
		if(writer != null){
			treeLog.log(treeLog.INFO,  "copy from cache, class " + data.substituteClassName);
			File tempFile = data.cacheImplFile;
			BufferedReader in = new BufferedReader(new FileReader(tempFile));
			char[] buf = new char[1024];
			int count = in.read(buf, 0, 1024);
			while(count >= 0){
				writer.write(buf, 0, count);
				count = in.read(buf, 0, 1024);
			}
			in.close();
			writer.close();
			data.ctx.commit(treeLog, writer);
		}
		treeLog.log(treeLog.INFO, "done");
		return data.packageName + "." + substituteClassName;
	}
}
